package client;

import connect.ConnectionData;
import connect.Picture;

import java.io.*;
import java.math.RoundingMode;
import java.net.*;
import java.text.DecimalFormat;
import java.util.*;

public class ClientKernel {
    public static final char MSGENDCHAR = 0xff;
    public static final char EXIT = 0xFE;
    public static final char NICK = 0xFD;
    public static final char COMMAND = 0xFD;

    public String serverAd;

    private int port;
    private Socket sock;
    private boolean isConnected = false;
    private boolean dropMe = false;
    private LinkedList clients;
    public String nick;
    public boolean printMsg = true;
    public boolean privateMsg = false;
    public String privateNick = "";
    private ClientMsgSender cms;
    private ClientMsgListener cml;

    /**
     * Creates a new instance of ClientKernel
     */
    public ClientKernel(String server, int port) {
        this.port = port;
        nick = "" + port;
        serverAd = server;
        clients = new LinkedList();
        connect();
        if (isConnected) {
            cms = new ClientMsgSender(this, sock);
            cml = new ClientMsgListener(this, sock);
        }
    }

    public void connect() {
        try {
            sock = new Socket(serverAd, port);
            isConnected = true;
        } catch (IOException ioe) {
            ioe.printStackTrace();
        }
    }

    public int getPort() {
        return port;
    }

    public boolean setNick(String nick) throws IOException {
        sendMessage("/nick " + nick);
        return true;
    }

    public int getLocalPort() {
        return sock.getLocalPort();
    }

    public void dropMe() {
        System.out.println("Drop ME!!!");
        cms.drop();
        cml.drop();
        dropMe = true;
        while (cml.hasStoped() && cms.hasStoped()) pause(5);
    }

    private final String MSG = "msg";
    private final String FILE = "file";

    public void sendMessage(String str) throws IOException {
        if (!dropMe) {
            if (str.charAt(0) == '/') {
                String toSend = str.substring(1);
                StringTokenizer strTok = new StringTokenizer(str.substring(1));
                String command = strTok.nextToken();
                if (command.equalsIgnoreCase(MSG)) {
                    String name = strTok.nextToken();
                    String msg = "You (private send) to " + name + ": ";
                    while (strTok.hasMoreTokens())
                        msg += strTok.nextToken() + " ";
                    this.storePrivateMsg(msg);
                } else if (command.equalsIgnoreCase(FILE)) {
                    if (!privateMsg) {
                        this.storePrivateMsg("不知道给谁发");
                        return;
                    }
                    ClientFileSender cfs = new ClientFileSender(this, new File("D:\\dili.jpg"));
                    toSend += " " + privateNick;
                    String msg = "You (private send) to " + privateNick + " FILE";
                    this.storePrivateMsg(msg);
                }
                ConnectionData con = new ConnectionData(toSend);
                con.setFunction();
                cms.addMessage(con);
            } else {
                if (!privateMsg)
                    cms.addMessage(new ConnectionData(str));
                else {
                    ConnectionData con = new ConnectionData(MSG + " " + privateNick + " " + str);
                    con.setFunction();
                    cms.addMessage(con);
                    String msg = "You (private send) to " + privateNick + ": " + str;
                    this.storePrivateMsg(msg);
                }
            }
        }
    }

    //发图片 发文件
    public void sendFile(File file) throws IOException {
        //获取文件后缀
        String filename = file.getName();
        String[] strArray = filename.split("\\.");
        int suffixIndex = strArray.length - 1;
        String last = strArray[suffixIndex];
        if (last.equals("jpg") || last.equals("png") || last.equals("gif")) {
            //发图片：私发或公发
            ConnectionData con = new ConnectionData(file);
            if (!privateMsg) {
                cms.addMessage(con);
            } else {
                con.setFunction();
                String msg = "";
                con.setToken(MSG + " " + privateNick);
                cms.addMessage(con);
                ConnectionData my = new ConnectionData(file);
                my.setToken("You (private send) to " + privateNick + ":");
                storePrivate(my);
            }
        } else {
            //发文件：只能通过FileSender私发
            ClientFileSender cfs = new ClientFileSender(this, file);
            String msg = "You (private send) to " + privateNick + " FILE";
            this.storePrivateMsg(msg);
            String toSend = FILE + " " + privateNick;
            ConnectionData con = new ConnectionData(toSend);
            con.setFunction();
            cms.addMessage(con);
        }
    }


    public void addClient(ChatClient c) {
        clients.add(c);
    }

    public void removeClient(ChatClient c) {
        clients.remove(c);
    }

    public void pause(int time) {
        try {
            Thread.sleep(time);
        } catch (Exception e) {
        }
    }

    public void store(ConnectionData con) throws IOException {
        if (con.isImage()) {
            storeImage(con);
        } else {
            storeMsg(con.getToken());
        }
    }

    public void storePrivate(ConnectionData con) throws IOException {
        if (con.isImage()) {
            storePrivateImage(con);
        } else {
            storePrivateMsg(con.getToken());
        }
    }

    public void storeImage(ConnectionData con) throws IOException {
        Picture picture = con.getPicture();
        if (picture == null) {
            System.out.println("未收到图片");
        } else {
            byte[] photo = picture.getPic_data();
            String name = picture.getPic_name();
            File file = new File(name);
            OutputStream out = new FileOutputStream(file);
            out.write(photo);
            out.close();

            String str = con.getToken() + "<br /><img src=\"" + name + "\"/>";
            storeMsg(str);
        }
    }

    public void storePrivateImage(ConnectionData con) throws IOException {
        Picture picture = con.getPicture();
        if (picture == null) {
            System.out.println("未收到图片");
        } else {
            byte[] photo = picture.getPic_data();
            String name = picture.getPic_name();
            File file = new File(name);
            OutputStream out = new FileOutputStream(file);
            out.write(photo);
            out.close();

            String str = con.getToken() + "<br /><img src=\"" + name + "\"/>";
            storePrivateMsg(str);
        }
    }

    public synchronized void storeMsg(String str) throws IOException {
        Object[] client = clients.toArray();
        for (int i = 0; i < client.length; i++)
            ((ChatClient) (client[i])).addMsg(str + "<br />");
    }

    public synchronized void storePrivateMsg(String str) throws IOException {
        Object[] client = clients.toArray();
        for (int i = 0; i < client.length; i++)
            ((ChatClient) (client[i])).addPrivateMsg(str + "<br />");
    }

    public boolean isConnected() {
        return isConnected;
    }

    public static void main(String args[]) {
        new ClientKernel("localhost", 1984);
    }

    //更改在线用户
    public void getusers(String str) {
        String temp[] = str.split(",");
        Object[] client = clients.toArray();
        for (int i = 0; i < client.length; i++)
            ((ChatClient) (client[i])).ChangeUsers(temp);
    }

    public void broadCastUsers() throws IOException {
        sendMessage("/users");
    }

    public void addFriend(String str) throws IOException {
        sendMessage("/friend " + str);
    }

    public void receiveFriend(String name) {
        Object[] client = clients.toArray();
        ChatClient cc = ((ChatClient) (client[0]));
        cc.receiveFriend(name);
    }

    public void acFriend(String name, String str) throws IOException {
        sendMessage("/acfriend " + name + " " + str);
    }

    public void rcFriend(String name, String str) throws IOException {
        if (str.equals("yes")) {
            storePrivateMsg(name + " 同意了您的好友请求！");
        } else {
            storePrivateMsg(name + "拒绝了您的好友请求！");
        }

    }

    public void imOnline() throws IOException {
        sendMessage("/online");
    }

    public void otherOnline(String b) throws IOException {
        Object[] client = clients.toArray();
        ChatClient cc = ((ChatClient) (client[0]));
        SQL LogIn = cc.LogIn;
        //判断b是否是a的好友
        if (LogIn.isfri(cc.usrin, b) == 1) {
            storePrivateMsg("您的好友 " + b + " 上线了！");
        }
    }

    public void getFile() throws IOException {
        ClientFileListener cfl = new ClientFileListener(this);
        this.storePrivateMsg("正在接收");
    }
}

class ClientMsgSender extends Thread {
    private Socket s;
    private ClientKernel ck;
    private LinkedList<ConnectionData> msgList;
    private boolean running = true;
    private boolean hasStoped = false;

    public ClientMsgSender(ClientKernel ck, Socket s) {
        this.ck = ck;
        this.s = s;
        msgList = new LinkedList();
        start();
    }

    public synchronized void addMessage(ConnectionData msg) {
        msgList.addLast(msg);
    }

    public void drop() {
        running = false;
    }

    public boolean hasStoped() {
        return hasStoped;
    }

    public void run() {
        ObjectOutputStream os = null;
        try {
            os = new ObjectOutputStream(s.getOutputStream());
            while (running) {
                while (msgList.size() > 0) {
                    Object con = ((Object) (msgList.removeFirst()));
                    os.writeObject(con);
                    os.flush();
                }
                sleep(10);
            }
            stop();
        } catch (Exception ioe) {
            ioe.printStackTrace();
        } finally {
            hasStoped = true;
            try {
                os.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

class ClientMsgListener extends Thread {
    private ClientKernel ck;
    private Socket s;
    private boolean running = true;
    private boolean hasStoped = false;

    public ClientMsgListener(ClientKernel ck, Socket s) {
        this.ck = ck;
        this.s = s;
        start();
    }

    public void drop() {
        running = false;
    }

    public boolean hasStoped() {
        return hasStoped;
    }

    public void run() {
        ObjectInputStream is = null;
        try {
            is = new ObjectInputStream(new BufferedInputStream(s.getInputStream()));
            while (running) {
                Object obj = is.readObject();
                ConnectionData con = (ConnectionData) obj;
                if (!con.getFunction())
                    ck.store(con);
                else {
                    runCommand(con);
                }
            }
            stop();
        } catch (IOException | ClassNotFoundException ioe) {
            ioe.printStackTrace();
        } finally {
            hasStoped = true;
            try {
                is.close();
            } catch (IOException e) {
            }
        }
    }

    private final String USERS = "users";
    private final String MSG = "msg";
    private final String FILE = "file";
    private final String ONLINE = "online";
    private final String FRIEND = "friend";
    private final String ACFRIEND = "acfriend";

    public void runCommand(ConnectionData con) throws IOException {
        try {
            StringTokenizer strTok = new StringTokenizer(con.getToken());
            String command = strTok.nextToken();
            if (command.equalsIgnoreCase(USERS)) {
                ck.getusers(strTok.nextToken());
            } else if (command.equalsIgnoreCase(MSG)) {
                String s = "";
                while (strTok.hasMoreTokens())
                    s += strTok.nextToken() + " ";
                con.setToken(s);
                ck.storePrivate(con);
            } else if (command.equalsIgnoreCase(FILE)) {
                ck.getFile();
                String s = "";
                while (strTok.hasMoreTokens())
                    s += strTok.nextToken() + " ";
                ck.storePrivateMsg(s);
            } else if (command.equalsIgnoreCase(ONLINE)) {
                String name = strTok.nextToken();
                ck.otherOnline(name);
            } else if (command.equalsIgnoreCase(FRIEND)) {
                String name = strTok.nextToken();
                ck.receiveFriend(name);
            } else if (command.equalsIgnoreCase(ACFRIEND)) {
                String name = strTok.nextToken();
                String str = strTok.nextToken();
                ck.rcFriend(name, str);
            }
        } catch (Exception e) {
            System.out.println("CommandParser: " + e.getMessage());
            ck.storeMsg("Invalid Command: " + con.getToken());
        }
    }
}

class ClientFileSender extends Thread {
    private ClientKernel ck;
    private ServerSocket sSock;

    private boolean running = true;
    private boolean hasStoped = false;
    private int port = 1999;

    private Socket sock;


    private File file;


    public ClientFileSender(ClientKernel ck, File file) {
        this.ck = ck;
        this.file = file;
        start();
    }

    public void drop() {
        running = false;
    }

    public boolean hasStoped() {
        return hasStoped;
    }

    public void run() {
        try {
            sSock = new ServerSocket(port);
            sock = sSock.accept();
        } catch (IOException e) {
            e.printStackTrace();
        }
        DataInputStream dis = null;
        DataOutputStream dos = null;
        RandomAccessFile rad = null;
        try {
            if (file.exists()) {
                dis = new DataInputStream(sock.getInputStream());
                dos = new DataOutputStream(sock.getOutputStream());

                FileInputStream fis = new FileInputStream(file);
                rad = new RandomAccessFile(file.getPath(), "r");

                dos.writeUTF(file.getName());
                dos.flush();

                byte[] buf = new byte[1024];

                long size = dis.readLong();//读取文件已发送的大小
                dos.writeLong(rad.length());
                dos.writeUTF("ok");
                dos.flush();

                long offset = size;//字节偏移量

                int barSize = (int) (rad.length() / 1024);
                int barOffset = (int) (offset / 1024);

                //从文件指定位置开始传输
                int length;
                if (offset < rad.length()) {
                    rad.seek(offset);
                    int last = 0;
                    while ((length = rad.read(buf)) > 0) {
                        dos.write(buf, 0, length);
                        ++barOffset;
                        dos.flush();
                        //打印百分比
                        int per = (int) ((float) 100 * barOffset / barSize);
                        if (last != per) {
                            System.out.println(per + "%");
                            last = per;
                        }
                    }
                }
                ck.storePrivateMsg("文件发送完成");

                dis.close();
                dos.close();
                rad.close();
            }

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
            try {
                ck.storePrivateMsg("文件发送中止");
            } catch (IOException ioException) {
                ioException.printStackTrace();
            }
        } finally {
            try {
                dis.close();
                dos.close();
                rad.close();
                sSock.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        stop();
    }
}

class ClientFileListener extends Thread {
    private ClientKernel ck;

    private boolean running = true;
    private boolean hasStoped = false;
    private int port = 1999;

    private Socket sock;

    private RandomAccessFile rad;


    public ClientFileListener(ClientKernel ck) {
        this.ck = ck;
        start();
    }

    public void drop() {
        running = false;
    }

    public boolean hasStoped() {
        return hasStoped;
    }

    public void run() {
        try {
            sock = new Socket(ck.serverAd, port);
        } catch (IOException e) {
            e.printStackTrace();
        }
        DataInputStream dis = null;
        DataOutputStream dos = null;
        try {
            dis = new DataInputStream(sock.getInputStream());
            dos = new DataOutputStream(sock.getOutputStream());
            String filename = dis.readUTF();

            File directory = new File("D:\\");
            if (!directory.exists()) {
                directory.mkdirs();
            }
            File file = new File(directory.getAbsolutePath() + File.separatorChar + filename + ".temp");
            rad = new RandomAccessFile(directory.getAbsolutePath() + File.separatorChar + filename + ".temp", "rw");

            //获得文件大小
            long size = 0;
            if (file.exists() && file.isFile()) {
                size = file.length();
                ck.storePrivateMsg("继续接收" + size);
            }

            dos.writeLong(size);//发送已接收的大小
            dos.flush();
            long allSize = dis.readLong();
            String rsp = dis.readUTF();

            int barSize = (int) (allSize / 1024);
            int barOffset = (int) (size / 1024);

            //接收文件
            if (rsp.equals("ok")) {
                rad.seek(size);
                int length;
                byte[] buf = new byte[1024];
                int last = 0;
                while ((length = dis.read(buf, 0, buf.length)) != -1) {
                    rad.write(buf, 0, length);
                    ++barOffset;

                    //打印百分比
                    int per = (int) (100 * barOffset / (float) barSize);
                    if (last != per) {
                        System.out.println(per + "%");
                        last = per;
                    }
                }
                System.out.println("FileReceive end...");
            }
            ck.storePrivateMsg("文件接收完成");

            dis.close();
            dos.close();
            rad.close();
            //文件重命名
            if (barOffset >= barSize) {
                file.renameTo(new File(directory.getAbsolutePath() + File.separatorChar + filename));
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                dis.close();
                dos.close();
                rad.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}